/*
 * Description: Implementation of bootloader logic.
 *
 * Created: 07-03-2013 16:08:48
 *  Author: Bootie
 */
#include "bootloader.h"
#include "../Headers/global_definitions.h"
#include <avr/interrupt.h>
#ifdef DEBUG_BOOTLOADER
#include "uart.h"
#endif

BOOTLOADER_SECTION
Bootloader::Bootloader()
{
    // Setup member
    _fileSystem = new Filesystemhandler(0);
    _fileHandler = new Filehandler();

    // Other
    Init();
}

BOOTLOADER_SECTION
Bootloader::~Bootloader()
{
    delete _fileHandler;
    delete _fileSystem;
}

/*
    Fix: atexit()
*/
extern "C" void   atexit( void ) { };

BOOTLOADER_SECTION
Bootloader& Bootloader::GetInstance()
{
    static Bootloader _instance;
    return _instance;
}

BOOTLOADER_SECTION
bool Bootloader::ProgramAvailable()
{
    PORTC &= ~1;
    // Check FileSystemHandler
    if(!_fileSystem->CheckFS())
        return false;
    PORTC &= ~2;
    // :Available
    // ::Check for program
    if(!_fileHandler->OpenFile(HEX_FILENAME, Filehandler::READ | Filehandler::OPEN_ALWAYS))
        return false;
    PORTC &= ~4 ;
    // :::Success
    return true;
}

/// \warning: No write location safety check.
BOOTLOADER_SECTION
void Bootloader::RunProgram()
{
    __asm("jmp 0x4E20"); // \WARNING: Static location
}

BOOTLOADER_SECTION
bool Bootloader::LoadProgram()
{
    // Variables
    char buffer[512];
    
    // Check for file
    if(!ProgramAvailable())
        return false;
    
    _fileHandler->OpenFile(HEX_FILENAME, Filehandler::READ | Filehandler::OPEN_ALWAYS);

    // Read loop
    while(_fileHandler->ReadFile(buffer, 512))
    {
	    int toWriteOut = 0;
	    int bytesAvailable = _fileHandler->FILE_BYTE_COUNT;
	    int writeOffSet = 0;

	    // Write buffer out
	    while(bytesAvailable > 0)
	    {
		    // :Delete old page
            cli(); // No interrupts while erasing
		    boot_page_erase(_writeAddress);
		    while (boot_rww_busy())
                {}
		    boot_rww_enable();
            sei();
		    
		    // :Split into pages
		    if(bytesAvailable > PAGESIZE)
		    {
			    toWriteOut = PAGESIZE;
			    bytesAvailable = bytesAvailable - PAGESIZE;
		    }
		    else
		    {
			    toWriteOut = bytesAvailable;
			    bytesAvailable = 0;
		    }

		    // :Write page
		    // ::Fill page
		    for(int i = 0; i < toWriteOut; i++)
		        boot_page_fill(i, buffer[writeOffSet + i]);

		    // ::Write page
            cli(); // No interrupts while writing
		    boot_page_write(_writeAddress);
            while (boot_rww_busy())
                {}
            boot_rww_enable();
            sei();
            
            #ifdef DEBUG_BOOTLOADER
            boot_rww_enable();
            for(int i = 0; i < toWriteOut; i++)
            {
            	USART_Transmit(pgm_read_byte_near(_writeAddress + i + 1));
                USART_Transmit(pgm_read_byte_near(_writeAddress + i));
            }   
            #endif
		    
		    _writeAddress += PAGESIZE;
		    writeOffSet += toWriteOut;
	    }
    }
    
    return true;
}

/************************************************************************/
/* Helper functions                                                     */
/************************************************************************/
BOOTLOADER_SECTION
void Bootloader::Init()
{
    // Values
    _writeAddress = APP_START;
    
    // Debugging
    #ifdef DEBUG_BOOTLOADER
    USART_Init(115200);
    USART_SendString("Uart set up.");
    #endif
}